package gateway

import (
	"errors"
	"fmt"
	"gitee.com/zackeus/go-boot/freeswitch/mod/xmlcurl"
	"gitee.com/zackeus/goutil/arrutil"
	"gitee.com/zackeus/goutil/strutil"
	"github.com/beevik/etree"
	"strconv"
)

type (
	Option func(xml *Xml) error

	Xml struct {
		doc       *etree.Document
		body      *etree.Element
		variables *etree.Element
	}
)

var (
	allowedTransports = []string{"tcp", "udp"}
	allowedDirections = []string{"inbound", "outbound"}
)

func New(name string, realm string, transport string, opts ...Option) (xmlcurl.XmlConf, error) {
	xml := &Xml{
		doc: etree.NewDocument(),
	}
	include := xml.doc.CreateElement("include")

	gateway := include.CreateElement("gateway")
	xml.body = gateway
	gateway.CreateAttr("name", name)

	/* 自定义变量 */
	variables := gateway.CreateElement("variables")
	xml.variables = variables

	/* 注册服务器 */
	xml.createParam("realm", realm)
	/* 传输方式 */
	if strutil.IsBlank(transport) || arrutil.NotIn(transport, allowedTransports) {
		return nil, errors.New("the register-transport is invalid")
	}
	xml.createParam("register-transport", transport)

	xml.createParam("register", strconv.FormatBool(false))

	for _, opt := range opts {
		if err := opt(xml); err != nil {
			return nil, err
		}
	}
	return xml, nil
}

// WithRegister 是否注册
func WithRegister(b bool) Option {
	return func(x *Xml) error {
		x.createParam("register", strconv.FormatBool(b))
		return nil
	}
}

// WithUserName SIP 用户名
func WithUserName(s string) Option {
	return func(x *Xml) error {
		if strutil.IsBlank(s) {
			x.removeParam("username")
			return nil
		}
		x.createParam("username", s)
		return nil
	}
}

// WithPassword SIP 密码
func WithPassword(s string) Option {
	return func(x *Xml) error {
		if strutil.IsBlank(s) {
			x.removeParam("password")
			return nil
		}
		x.createParam("password", s)
		return nil
	}
}

// WithPing 发送 SIP OPTIONS 间隔 如果失败会从该网关注销 状态置为down
func WithPing(n uint64) Option {
	return func(x *Xml) error {
		if n == 0 {
			x.removeParam("ping")
			return nil
		}
		x.createParam("ping", strconv.FormatUint(n, 10))
		return nil
	}
}

// WithExpireSeconds 注册间隔(s)
func WithExpireSeconds(n uint64) Option {
	return func(x *Xml) error {
		if n == 0 {
			x.removeParam("expire-seconds")
			return nil
		}
		x.createParam("expire-seconds", strconv.FormatUint(n, 10))
		return nil
	}
}

// WithRegisterProxy 注册代理服务器
func WithRegisterProxy(s string) Option {
	return func(x *Xml) error {
		if strutil.IsBlank(s) {
			x.removeParam("register-proxy")
			return nil
		}
		x.createParam("register-proxy", s)
		return nil
	}
}

// WithOutboundProxy 出局代理服务器
func WithOutboundProxy(s string) Option {
	return func(x *Xml) error {
		if strutil.IsBlank(s) {
			x.removeParam("outbound-proxy")
			return nil
		}
		x.createParam("outbound-proxy", s)
		return nil
	}
}

// WithFromDomain 指定域 会影响SIP中的 "From" 头域
func WithFromDomain(s string) Option {
	return func(x *Xml) error {
		if strutil.IsBlank(s) {
			x.removeParam("from-domain")
			return nil
		}
		x.createParam("from-domain", s)
		return nil
	}
}

// WithFromUser 设置SIP消息中From字段值 没有配置则默认和username相同
func WithFromUser(s string) Option {
	return func(x *Xml) error {
		if strutil.IsBlank(s) {
			x.removeParam("from-user")
			return nil
		}
		x.createParam("from-user", s)
		return nil
	}
}

// WithCallerIdInFrom 将主叫号码放到SIP的From字段中, 此设置优先级比from-user高
func WithCallerIdInFrom(b bool) Option {
	return func(x *Xml) error {
		x.createParam("caller-id-in-from", strconv.FormatBool(b))
		return nil
	}
}

// WithVariable 自定义网关变量 sofia_gateway_data ${gwName} var ${varName}
// direction: 设置方向(inbound|outbound) 不设置则默认全部方向
func WithVariable(name string, value string, direction ...string) Option {
	return func(x *Xml) error {
		variable := x.variables.FindElement(fmt.Sprintf("./variable[@name='%s']", name))
		if variable == nil {
			variable = x.variables.CreateElement("variable")
			variable.CreateAttr("name", name)
		}

		variable.CreateAttr("value", value)

		if direction == nil {
			variable.RemoveAttr("direction")
		} else {
			if strutil.IsBlank(direction[0]) || arrutil.NotIn(direction[0], allowedDirections) {
				return errors.New("the variable direction is invalid")
			}
			variable.CreateAttr("direction", direction[0])
		}
		return nil
	}
}

func (x *Xml) createParam(name string, value string) {
	param := x.body.FindElement(fmt.Sprintf("./param[@name='%s']", name))
	if param == nil {
		param = x.body.CreateElement("param")
	}

	param.CreateAttr("name", name)
	param.CreateAttr("value", value)
}

func (x *Xml) removeParam(name string) {
	param := x.body.FindElement(fmt.Sprintf("./param[@name='%s']", name))
	if param != nil {
		x.body.RemoveChild(param)
	}
}

func (x *Xml) BodyElement() *etree.Element {
	return x.body
}

func (x *Xml) MarshalToXml() ([]byte, error) {
	x.doc.Indent(1)
	return x.doc.WriteToBytes()
}

func (x *Xml) UnMarshalFromString(v string) error {
	if x.doc == nil {
		x.doc = etree.NewDocument()
	}
	if err := x.doc.ReadFromString(v); err != nil {
		return err
	}

	gateway := x.doc.FindElement("./include/gateway")
	if gateway == nil {
		return errors.New("the element include/gateway is nil")
	}
	x.body = gateway

	variables := x.doc.FindElement("./include/gateway/variables")
	if variables == nil {
		return errors.New("the element include/gateway/variables is nil")
	}
	x.variables = variables

	return nil
}

func (x *Xml) WriteToFile(path string) error {
	x.doc.Indent(1)
	return x.doc.WriteToFile(path)
}
